package com.hp.test.interview.multithreaded;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Date 03/05/2015
 *
 * @author tusroy
 * <p>
 * Write a program to count words. This program should be threadsafe.
 * Implement two apis
 * 1) void addWord(String word) -> increment count of this word
 * 2) long getCount(String word) -> get count of this word
 * <p>
 * Solution
 * Keep a concurrent map. Key to this map should be word while value should be AtomicLong to update it
 * in threadsafe way
 * <p>
 * UnicodeMaxIntValue cases
 * One word updated by many threads
 * Many words updated by many threads
 * @Threadsafe
 */
public class CountingWord {

    private ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<>();

    public static void main(String args[]) throws InterruptedException {
        ExecutorService executor1 = Executors.newFixedThreadPool(20);
        ExecutorService executor2 = Executors.newFixedThreadPool(20);
        ExecutorService executor3 = Executors.newFixedThreadPool(20);

        int total = 100000;
        CountDownLatch countDownLatch = new CountDownLatch(3 * total);
        CountingWord cw = new CountingWord();
        for (int i = 0; i < total; i++) {
            executor1.execute(() -> cw.addWord("word1"));
            countDownLatch.countDown();
        }
        for (int i = 0; i < total; i++) {
            executor2.execute(() -> cw.addWord("word2"));
            countDownLatch.countDown();
        }
        for (int i = 0; i < total; i++) {
            executor3.execute(() -> cw.addWord("word3"));
            countDownLatch.countDown();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw e;
        }
        executor1.shutdownNow();
        executor2.shutdownNow();
        executor3.shutdownNow();

        long count1 = cw.getCount("word1");
        long count2 = cw.getCount("word2");
        long count3 = cw.getCount("word3");
        assert count1 == total;
        assert count2 == total;
        assert count3 == total;

    }

    public void addWord(String word) {
        AtomicLong l = map.get(word);
        if (l == null) {
            l = new AtomicLong(1);
            l = map.putIfAbsent(word, l);
            if (l != null) {
                l.incrementAndGet();
            }
        } else {
            l.incrementAndGet();
        }
    }

    public long getCount(String word) {
        AtomicLong l = map.get(word);
        if (l != null) {
            return l.longValue();
        }
        return 0;
    }
}
